﻿using System.Threading.Tasks;
using NUnit.Framework;
using BookSleeve;
using System.Diagnostics;

namespace Tests
{
    [TestFixture]
    public class Transactions // http://redis.io/commands#transactions
    {
        [Test]
        public void TestBasicMultiExec()
        {
            using (var conn = Config.GetUnsecuredConnection(allowTransactions: true))
            {
                conn.Keys.Remove(1, "tran");
                conn.Keys.Remove(2, "tran");

                conn.Transactions.Multi();
                var s1 = conn.Strings.Set(1, "tran", "abc");
                var s2 = conn.Strings.Set(2, "tran", "def");
                var g1 = conn.Strings.GetString(1, "tran");
                var g2 = conn.Strings.GetString(2, "tran");
                var exec = conn.Transactions.Exec();

                Assert.AreEqual("abc", conn.Wait(g1));
                Assert.AreEqual("def", conn.Wait(g2));
                conn.Wait(s1);
                conn.Wait(s2);
                conn.Wait(exec);
            }
        }

        [Test]
        public void TestEmptyList()
        {
            using (var conn = Config.GetUnsecuredConnection(allowTransactions: true))
            {
                conn.Transactions.Multi();
                var exec = conn.Transactions.Exec();
                Assert.AreEqual(0, conn.Wait(exec));
            }
        }

        [Test]
        public void TestExecOrDiscardNoNMultiFails()
        {
            using (var conn = Config.GetUnsecuredConnection(allowTransactions: true))
            {
                var exec = conn.Transactions.Exec();
                try
                {
                    conn.Wait(exec);
                    Assert.Fail("Exec without a preceding Multi should fail");
                }
                catch (RedisException) { }  // TODO: finer-grained

                var discard = conn.Transactions.Discard();
                try
                {
                    conn.Wait(discard);
                    Assert.Fail("Discard with a preceding Multi should fail");
                }
                catch (RedisException) { }
            }
        }

        [Test]
        public void TestDiscard()
        {
            using (var conn = Config.GetUnsecuredConnection(allowTransactions: true))
            {
                conn.Transactions.Multi();
                var task = conn.Strings.Set(4, "abc", "def");
                conn.Transactions.Discard();

                try
                {
                    conn.Wait(task);
                }
                catch (TaskCanceledException)
                { }// ok, else boom!
            }
        }

        [Test]
        public void TestDiscardCancelsMulti()
        {
            using (var conn = Config.GetUnsecuredConnection(allowTransactions: true))
            {
                conn.Transactions.Multi();
                var discard = conn.Transactions.Discard();
                var multi2 = conn.Transactions.Multi();

                conn.Wait(multi2);
            }
        }

        [Test]
        public void TestDuplicateMultiFails()
        {
            using (var conn = Config.GetUnsecuredConnection(allowTransactions: true))
            {
                conn.Transactions.Multi();
                var task = conn.Transactions.Multi();
                try
                {
                    conn.Wait(task);
                    Assert.Fail("Multi after a Multi should fail");
                }
                catch (RedisException) { }  // TODO: finer-grained
            }
        }

        [Test]
        public void TestBasicWatch()
        {
            using (var conn = Config.GetUnsecuredConnection(allowTransactions: true))
            {
                conn.Wait(conn.Transactions.Watch(9, "watched"));
            }
        }
        
        [Test]
        public void TestBasicUnwatch()
        {
            using (var conn = Config.GetUnsecuredConnection(allowTransactions: true))
            {
                conn.Transactions.Watch(9, "watched");
                conn.Wait(conn.Transactions.Unwatch());
            }
        }

        [Test]
        public void TestTransactionAbort()
        {
            using (var conn1 = Config.GetUnsecuredConnection(allowTransactions: true))
            using (var conn2 = Config.GetUnsecuredConnection(allowTransactions: true))
            {
                conn1.Keys.Remove(9, "shouldntChange");
                conn1.Keys.Remove(9, "watched");

                conn1.Wait(conn1.Transactions.Watch(9, "watched"));
                
                conn1.Transactions.Multi();
                conn1.Strings.Set(9, "shouldntChange", "changed");

                var set = conn2.Strings.Set(9, "watched", "booyah");
                conn2.Wait(set);

                var exec = conn1.Transactions.Exec();

                Assert.AreEqual(0, conn1.Wait(exec));
                Assert.IsFalse(conn1.Wait(conn1.Keys.Exists(9, "shouldntChange")));
            }
        }

        [Test]
        public void TestRace()
        {
            using (var conn1 = Config.GetUnsecuredConnection(allowTransactions: true))
            using (var conn2 = Config.GetUnsecuredConnection(allowTransactions: true))
            {
                conn1.Wait(conn1.Keys.Remove(9, new string[] { "conn1", "conn2", "winner" }));

                conn1.Wait(conn1.Transactions.Watch(9, "winner"));
                conn2.Wait(conn2.Transactions.Watch(9, "winner"));
                
                conn1.Transactions.Multi();
                conn1.Strings.Set(9, "winner", "conn1 won");
                conn1.Strings.Set(9, "conn1", "wasSet");

                conn2.Transactions.Multi();
                conn2.Strings.Set(9, "winner", "conn2 won");
                conn2.Strings.Set(9, "conn2", "wasSet");

                var c1 = conn1.Wait(conn1.Transactions.Exec());
                var c1s = conn1.Wait(conn1.Strings.GetString(9, "conn1"));
                var c2 = conn2.Wait(conn2.Transactions.Exec());
                var c2s = conn2.Wait(conn2.Strings.GetString(9, "conn2"));
                var w = conn1.Wait(conn1.Strings.GetString(9, "winner"));

                Trace.TraceInformation("c1, c1s, c2, c2s, w = {0}, {1}, {2}, {3}, {4}", c1, c1s, c2, c2s, w);

                Assert.AreNotEqual(c1, c2);
                Assert.That(c1 == 2 && c2 == 0 || c1 == 0 && c2 == 2);
                Assert.That(c1 == 2 && c1s == "wasSet" && c2s == null ||
                            c2 == 2 && c2s == "wasSet" && c1s == null);
                // etc.
            }
        }
    }
}

